Java 26일 코스 - Day 13: 추상 클래스와 인터페이스

Day 13: 추상 클래스와 인터페이스

추상 클래스와 인터페이스는 객체지향 설계의 핵심 도구입니다. 추상 클래스는 “부분적으로 완성된 설계도”이고, 인터페이스는 “계약서(규격)“입니다. 둘 다 직접 인스턴스를 만들 수 없으며, 자식 클래스에서 구체적으로 구현해야 합니다.

추상 클래스

abstract 키워드로 선언하며, 추상 메서드(본문 없는 메서드)와 일반 메서드를 모두 가질 수 있습니다.

abstract class GameCharacter {
    String name;
    int hp;
    int attackPower;

    GameCharacter(String name, int hp, int attackPower) {
        this.name = name;
        this.hp = hp;
        this.attackPower = attackPower;
    }

    // 추상 메서드: 자식 클래스가 반드시 구현해야 함
    abstract void attack(GameCharacter target);
    abstract void specialSkill();

    // 일반 메서드: 공통 로직
    void takeDamage(int damage) {
        hp -= damage;
        System.out.println(name + "이(가) " + damage + " 데미지를 받음! (HP: " + hp + ")");
        if (hp <= 0) {
            System.out.println(name + " 쓰러짐!");
        }
    }

    void showStatus() {
        System.out.println("[" + name + "] HP: " + hp + " / 공격력: " + attackPower);
    }
}

class Warrior extends GameCharacter {
    Warrior(String name) {
        super(name, 200, 30);
    }

    @Override
    void attack(GameCharacter target) {
        System.out.println(name + "이(가) 검으로 " + target.name + "을(를) 공격!");
        target.takeDamage(attackPower);
    }

    @Override
    void specialSkill() {
        System.out.println(name + "의 분노의 일격! (공격력 2배)");
        attackPower *= 2;
    }
}

class Mage extends GameCharacter {
    int mana;

    Mage(String name) {
        super(name, 100, 50);
        this.mana = 150;
    }

    @Override
    void attack(GameCharacter target) {
        if (mana >= 20) {
            System.out.println(name + "이(가) 파이어볼로 " + target.name + "을(를) 공격!");
            target.takeDamage(attackPower);
            mana -= 20;
        } else {
            System.out.println("마나 부족!");
        }
    }

    @Override
    void specialSkill() {
        System.out.println(name + "의 대규모 메테오!");
        mana -= 80;
    }
}

public class AbstractClassExample {
    public static void main(String[] args) {
        // GameCharacter gc = new GameCharacter(...); // 에러! 추상 클래스 인스턴스 불가
        Warrior warrior = new Warrior("전사 아서");
        Mage mage = new Mage("마법사 멀린");

        warrior.showStatus();
        mage.showStatus();

        warrior.attack(mage);
        mage.specialSkill();
        mage.attack(warrior);
    }
}

인터페이스

인터페이스는 클래스가 구현해야 할 메서드 규격을 정의합니다. implements로 구현하며, 다중 구현이 가능합니다.

interface Flyable {
    void fly();
    int getMaxAltitude();
}

interface Swimmable {
    void swim();
    int getMaxDepth();
}

interface Runnable {
    void run();
    int getMaxSpeed();
}

// 다중 인터페이스 구현
class Duck implements Flyable, Swimmable, Runnable {
    String name;

    Duck(String name) {
        this.name = name;
    }

    @Override
    public void fly() {
        System.out.println(name + "이(가) 하늘을 날아갑니다!");
    }

    @Override
    public int getMaxAltitude() {
        return 500;
    }

    @Override
    public void swim() {
        System.out.println(name + "이(가) 물 위에서 헤엄칩니다!");
    }

    @Override
    public int getMaxDepth() {
        return 2;
    }

    @Override
    public void run() {
        System.out.println(name + "이(가) 뒤뚱뒤뚱 달립니다!");
    }

    @Override
    public int getMaxSpeed() {
        return 10;
    }
}

class Penguin implements Swimmable, Runnable {
    String name;

    Penguin(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println(name + "이(가) 빠르게 수영합니다!");
    }

    @Override
    public int getMaxDepth() {
        return 100;
    }

    @Override
    public void run() {
        System.out.println(name + "이(가) 쪼르르 걸어갑니다!");
    }

    @Override
    public int getMaxSpeed() {
        return 5;
    }
}

public class InterfaceExample {
    // 인터페이스 타입으로 매개변수 선언 (다형성)
    static void letItFly(Flyable f) {
        f.fly();
        System.out.println("최대 고도: " + f.getMaxAltitude() + "m");
    }

    static void letItSwim(Swimmable s) {
        s.swim();
        System.out.println("최대 수심: " + s.getMaxDepth() + "m");
    }

    public static void main(String[] args) {
        Duck duck = new Duck("도널드");
        Penguin penguin = new Penguin("뽀로로");

        letItFly(duck);
        letItSwim(duck);
        letItSwim(penguin);
        // letItFly(penguin); // 컴파일 에러! Penguin은 Flyable이 아님
    }
}

default 메서드와 static 메서드

Java 8부터 인터페이스에도 구현된 메서드를 정의할 수 있습니다.

interface Logger {
    // 추상 메서드
    void log(String message);

    // default 메서드: 기본 구현 제공
    default void info(String message) {
        log("[INFO] " + message);
    }

    default void error(String message) {
        log("[ERROR] " + message);
    }

    default void warn(String message) {
        log("[WARN] " + message);
    }

    // static 메서드: 인터페이스 이름으로 호출
    static Logger consoleLogger() {
        return message -> System.out.println(message);
    }
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // 파일에 기록하는 대신 여기서는 콘솔에 표시
        System.out.println("[FILE] " + message);
    }

    // 필요하면 default 메서드를 오버라이딩할 수 있음
    @Override
    public void error(String message) {
        log("[CRITICAL ERROR] " + message);
    }
}

public class DefaultMethodExample {
    public static void main(String[] args) {
        Logger console = Logger.consoleLogger();
        console.info("서버 시작됨");
        console.error("연결 실패");
        console.warn("메모리 부족");

        Logger fileLogger = new FileLogger();
        fileLogger.info("요청 처리");
        fileLogger.error("디스크 공간 부족"); // 오버라이딩된 버전
    }
}

추상 클래스 vs 인터페이스 비교

// 추상 클래스: "~은 ~이다" (is-a 관계)
// - 상태(필드)를 가질 수 있음
// - 생성자를 가질 수 있음
// - 단일 상속만 가능
// - 공통 로직 + 강제 구현 메서드가 필요할 때

abstract class Vehicle {
    int speed;
    abstract void move();
    void stop() { speed = 0; }
}

// 인터페이스: "~을 할 수 있다" (can-do 관계)
// - 상태를 가질 수 없음 (상수만 가능)
// - 생성자 없음
// - 다중 구현 가능
// - 기능(역할)을 정의할 때

interface Chargeable {
    void charge();
    int getBatteryLevel();
}

interface GPSEnabled {
    String getCurrentLocation();
}

// 조합: 추상 클래스 상속 + 인터페이스 다중 구현
class ElectricScooter extends Vehicle implements Chargeable, GPSEnabled {
    int battery = 100;

    @Override
    void move() { System.out.println("전동킥보드가 달립니다!"); }

    @Override
    public void charge() { battery = 100; }

    @Override
    public int getBatteryLevel() { return battery; }

    @Override
    public String getCurrentLocation() { return "서울시 강남구"; }
}

오늘의 연습문제

  1. 결제 시스템 설계: Payable 인터페이스(메서드: pay(long amount), refund(long amount))를 정의하고, CreditCard, BankAccount, CryptoCurrency 클래스로 구현하세요.

  2. 정렬 가능한 컬렉션: Comparable<T> 인터페이스를 구현하는 Student 클래스를 만드세요. 이름과 점수를 가지며, 점수 기준 내림차순으로 정렬됩니다. Arrays.sort()로 테스트하세요.

  3. 게임 캐릭터 시스템: 추상 클래스 Character(이름, HP)와 인터페이스 Healable, Buffable을 조합하세요. Paladin은 두 인터페이스를 모두 구현하고, RogueBuffable만 구현합니다.

이 글이 도움이 되었나요?